package com.nn.algorithm;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;

/**
 * 
 * Backpropagation training
 * 
 */
public class Training {

	
	private double[][] input;
	private double[][] idealOutput;
	private Network network;
	private HashMap<Layer, BackPropagationLayer> layerMap;
	private double learnRate;
	private double momentum;
	private double error;

	public Training(Network network, double[][] input, double[][] idealOutput,
			double learnRate, double momentum) {
		this.network = network;
		this.input = input;
		this.idealOutput = idealOutput;
		this.learnRate = learnRate;
		this.momentum = momentum;
		layerMap = new HashMap<>();

		//output layer setup
		Layer output = network.getLayer(network.getLayers().size() - 1);
		//output layer has no propagation layer
		BackPropagationLayer previous = new BackPropagationLayer(output, null);
		layerMap.put(output, previous);

		for (int i = network.getLayers().size() - 2; i >= 0; i--) {
			Layer layer = network.getLayers().get(i);
			previous = new BackPropagationLayer(layer, previous);
			layerMap.put(layer, previous);
		}

	}

	public void iteration() {

		// for every input seat
		for (int i = 0; i < input.length; i++) {
			// calculate outputs
			double output[] = network.getNetworkOutput(input[i]);
			calculateError(idealOutput[i]);
		}

		// learn for every backpropagation layer
		for (BackPropagationLayer backPropagationLayer : layerMap.values()) {
			backPropagationLayer.learn(learnRate, momentum);
		}

		// calculate network error
		this.error = this.network.calculateError(input, idealOutput);
	}

	public double getError() {
		return this.error;
	}

	private void calculateError(double[] idealOutput) {

		// clear existing errors in the layers
		for (Layer layer : network.getLayers()) {
			BackPropagationLayer bLayer = layerMap.get(layer);
			bLayer.clearError();
		}

		// iterate backwards over the layers
		for (int i = network.getLayers().size() - 1; i >= 0; i--) {
			Layer layer = network.getLayers().get(i);
			layerMap.get(layer).calculateError(idealOutput);
		}
	}
	
	public void train()
	{
		int iteration = 0;
		do
		{
			iteration();
			System.out.println("Iteration:"+iteration+" Error:"+error);
			iteration++;
		}while((iteration<40000) && (error > 0.001));
		
		int index=0;
		for(Layer layer: network.getLayers())
		{
			if(layer.getLayerType() != LayerType.OUTPUT)
			{
				Persistence.saveMatrix(index++, layer.getWeightMatrix());
			}
		}
	}
	
	public static void testXOR(){
		double input[] = { 1.0, 2.0 };
		Network network = Network.buildNetworkWithOneHiddenLayer_XOR_TEST();
		network.getLayer(0).processLayer(input);
		network.getLayer(1).processLayer(null);
		network.getLayer(2).processLayer(null);
		System.out.println(Arrays.toString(input));
		System.out.println(Arrays.toString(network.getNetworkOutput(input)));

		double XOR_INPUT[][] = { { 0.0, 0.0 }, { 1.0, 0.0 }, { 0.0, 1.0 },{ 1.0, 1.0 } };
		double XOR_IDEAL[][] = { { 0.0 }, { 1.0 }, { 1.0 }, { 0.0 } };
		Training training = new Training(network, XOR_INPUT, XOR_IDEAL, 0.7,0.9);
		training.train();

		System.out.println(Arrays.toString(network.getNetworkOutput(new double[] { 0.0, 0.0 })));
		System.out.println(Arrays.toString(network.calculateRoundedOutput(new double[] { 0.0, 0.0 })));

		System.out.println(Arrays.toString(network.getNetworkOutput(new double[] { 1.0, 0.0 })));
		System.out.println(Arrays.toString(network.calculateRoundedOutput(new double[] { 1.0, 0.0 })));

		System.out.println(Arrays.toString(network.getNetworkOutput(new double[] { 0.0, 1.0 })));
		System.out.println(Arrays.toString(network.calculateRoundedOutput(new double[] { 0.0, 1.0 })));

		System.out.println(Arrays.toString(network.getNetworkOutput(new double[] { 1.0, 1.0 })));
		System.out.println(Arrays.toString(network.calculateRoundedOutput(new double[] { 1.0, 1.0 })));

	}

	public static void main(String[] args) {
		
		Network network = Network.buildNetworkWithOneHiddenLayer(true);
		
		int numberOfInputs = 72;
		int numberOfTrainingSets = 20;
		int numberOfTestSets = 60 - numberOfTrainingSets;
		
		//standing data
		double input_from_file_standing[][] = Persistence.readInput("Standing.txt");
		double input_for_testing_standing[][] = new double[numberOfTestSets][numberOfInputs];
		for(int i=0; i<numberOfTestSets; i++)
		{
			input_for_testing_standing[i] = input_from_file_standing[numberOfTrainingSets+i];
		}
		double[][] ideal_output_standing = Persistence.generateIdealOutput(numberOfTrainingSets, 0);
		
		
		//running data
		double input_from_file_running[][] = Persistence.readInput("Running.txt");
		double input_for_testing_running[][] = new double[numberOfTestSets][numberOfInputs];
		for(int i=0; i<numberOfTestSets; i++)
		{
			input_for_testing_running[i] = input_from_file_running[numberOfTrainingSets+i];
		}
		double[][] ideal_output_running = Persistence.generateIdealOutput(numberOfTrainingSets, 2);

		//walking data
		double input_from_file_walking[][] = Persistence.readInput("Walking.txt");
		double input_for_testing_walking[][] = new double[numberOfTestSets][numberOfInputs];
		for(int i=0; i<numberOfTestSets; i++)
		{
			input_for_testing_walking[i] = input_from_file_walking[numberOfTrainingSets+i];
		}
		double[][] ideal_output_walking = Persistence.generateIdealOutput(numberOfTrainingSets, 1);
		
		
		double[][] input_training = new double[numberOfTrainingSets*3][numberOfInputs];
		double[][] ideal_output = new double[numberOfTrainingSets*3][3];
		for(int i=0; i<numberOfTrainingSets; i++)
		{
			input_training[i] = input_from_file_running[i];
			input_training[numberOfTrainingSets+i] = input_from_file_walking[i];
			input_training[numberOfTrainingSets*2 + i] = input_from_file_standing[i];
			ideal_output[i] = ideal_output_running[i];
			ideal_output[numberOfTrainingSets+i] = ideal_output_walking[i];
			ideal_output[numberOfTrainingSets*2 + i] = ideal_output_standing[i];
		}
		
		Training training = new Training(network, input_training, ideal_output, 0.01,0.9);
		training.train();
		
		int correct_running = 0;
		int correct_walking = 0;
		int correct_standing = 0;
		
		System.out.println("Running results");
		for(int i=0; i<numberOfTestSets; i++)
		{
			System.out.println(Arrays.toString(network.getNetworkOutput(input_for_testing_running[i])));
			System.out.println(Arrays.toString(network.calculateRoundedOutput(input_for_testing_running[i])));
			if(Arrays.equals(network.calculateRoundedOutput(input_for_testing_running[i]), ideal_output_running[0]))
			{
				correct_running++;
			}
			System.out.println();
		}
		
		System.out.println("Walking results");
		for(int i=0; i<numberOfTestSets; i++)
		{
			System.out.println(Arrays.toString(network.getNetworkOutput(input_for_testing_walking[i])));
			System.out.println(Arrays.toString(network.calculateRoundedOutput(input_for_testing_walking[i])));
			if(Arrays.equals(network.calculateRoundedOutput(input_for_testing_walking[i]), ideal_output_walking[0]))
			{
				correct_walking++;
			}
			System.out.println();
		}
		
		System.out.println("Standing results");
		for(int i=0; i<numberOfTestSets; i++)
		{
			System.out.println(Arrays.toString(network.getNetworkOutput(input_for_testing_standing[i])));
			System.out.println(Arrays.toString(network.calculateRoundedOutput(input_for_testing_standing[i])));
			if(Arrays.equals(network.calculateRoundedOutput(input_for_testing_standing[i]), ideal_output_standing[0]))
			{
				correct_standing++;
			}
			System.out.println();
		}
		
		System.out.println("Correct running: "+ correct_running + " out of "+ numberOfTestSets);
		System.out.println("Correct walking: "+ correct_walking + " out of "+ numberOfTestSets);
		System.out.println("Correct standing: "+ correct_standing + " out of "+ numberOfTestSets);
		
	}
}
